//! Database-specific implementation of the generic resource manager.

use super::{RegionalError, RegionalResult};
use async_trait::async_trait;
use price_common::resource::{ResourceCache, ResourceContext, ResourceError, ResourceManager, ResourceMetadata, ResourceRegistry, ResourceResult};
use rbatis::RBatis;
use rbdc_sqlite::Driver;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::RwLock;

/// Manages database connections keyed by a [`ResourceContext`].
pub struct DataSourceManager {
    registry: Arc<RwLock<ResourceRegistry>>,
    cache: ResourceCache<RBatis>,
    registry_path: Option<PathBuf>,
}

impl DataSourceManager {
    /// Create a manager with an empty in-memory registry.
    pub fn new() -> Self {
        Self::with_registry(ResourceRegistry::new())
    }

    /// Create a manager initialised from an existing registry.
    pub fn with_registry(registry: ResourceRegistry) -> Self {
        Self { registry: Arc::new(RwLock::new(registry)), cache: ResourceCache::new(10), registry_path: None }
    }

    /// Create a manager backed by a registry file on disk.
    ///
    /// The registry will be loaded lazily on [`initialize`](Self::initialize).
    pub fn with_registry_path(registry_path: impl Into<PathBuf>) -> Self {
        Self { registry: Arc::new(RwLock::new(ResourceRegistry::new())), cache: ResourceCache::new(10), registry_path: Some(registry_path.into()) }
    }

    /// Override the maximum number of cached connections.
    pub fn with_max_cached_connections(
        mut self,
        max: usize,
    ) -> Self {
        self.cache = ResourceCache::new(max);
        self
    }

    /// Load the registry from disk if a registry path has been provided.
    pub async fn initialize_registry(&self) -> RegionalResult<()> {
        if let Some(path) = &self.registry_path {
            if path.exists() {
                let registry =
                    ResourceRegistry::load_from_file(path).map_err(|e| RegionalError::ConfigError(format!("load registry failed: {e}")))?;
                *self.registry.write().await = registry;
            }
        }

        Ok(())
    }

    /// Register metadata for a new datasource.
    pub async fn register_metadata(
        &self,
        metadata: ResourceMetadata,
    ) -> RegionalResult<()> {
        let mut registry = self.registry.write().await;
        registry.register(metadata);
        self.persist_registry(&registry)
    }

    /// Remove a datasource and evict it from the cache.
    pub async fn unregister(
        &self,
        key: &str,
    ) -> RegionalResult<()> {
        {
            let mut registry = self.registry.write().await;
            registry.unregister(key);
            self.persist_registry(&registry)?;
        }

        self.cache.remove(key).await;
        Ok(())
    }

    /// Clear all cached connections.
    pub async fn clear_cache(&self) {
        self.cache.clear().await;
    }

    /// Cache statistics: (current size, max size).
    pub async fn cache_stats(&self) -> (usize, usize) {
        self.cache.stats().await
    }

    /// Convenience wrapper that returns a connection for the provided code.
    pub async fn get_connection(
        &self,
        context: &ResourceContext,
        code: &str,
    ) -> RegionalResult<Arc<RBatis>> {
        self.load_resource(context, code).await.map_err(|err| RegionalError::from_resource_error(err, context, code))
    }

    /// Convenience helper that loads every fallback level.
    pub async fn get_connections_with_fallback(
        &self,
        context: &ResourceContext,
    ) -> RegionalResult<Vec<(String, Arc<RBatis>)>> {
        let mut connections = Vec::new();

        for code in context.hierarchy.fallback_order() {
            match self.load_resource(context, &code).await {
                Ok(conn) => connections.push((code, conn)),
                Err(_) => continue,
            }
        }

        if connections.is_empty() {
            return Err(RegionalError::DataSourceNotFound(context.current_code().to_string(), context.resource_key(context.current_code())));
        }

        Ok(connections)
    }

    /// Reload the registry from disk and drop all cached connections.
    pub async fn refresh_registry(&self) -> RegionalResult<()> {
        self.cache.clear().await;
        self.initialize_registry().await
    }

    async fn create_connection(
        &self,
        db_path: &str,
    ) -> RegionalResult<Arc<RBatis>> {
        let rb = RBatis::new();
        rb.link(Driver {}, &format!("sqlite://{db_path}"))
            .await
            .map_err(|e| RegionalError::ConnectionFailed(format!("connect database failed: {e}")))?;

        Ok(Arc::new(rb))
    }

    fn persist_registry(
        &self,
        registry: &ResourceRegistry,
    ) -> RegionalResult<()> {
        if let Some(path) = &self.registry_path {
            registry.save_to_file(path).map_err(|e| RegionalError::ConfigError(format!("save registry failed: {e}")))?;
        }

        Ok(())
    }
}

#[async_trait]
impl ResourceManager<RBatis> for DataSourceManager {
    async fn load_resource(
        &self,
        context: &ResourceContext,
        code: &str,
    ) -> ResourceResult<Arc<RBatis>> {
        let key = context.resource_key(code);

        if let Some(conn) = self.cache.get(&key).await {
            return Ok(conn);
        }

        let registry = self.registry.read().await;
        let metadata = registry.get(&key).ok_or_else(|| ResourceError::ResourceNotFound(key.clone()))?;
        let db_path = metadata.path.clone();
        drop(registry);

        let conn = self.create_connection(db_path.to_string_lossy().as_ref()).await.map_err(|e| ResourceError::LoadFailed(e.to_string()))?;

        self.cache.insert(key, conn.clone()).await;

        Ok(conn)
    }

    async fn initialize(&self) -> ResourceResult<()> {
        self.initialize_registry().await.map_err(|e| ResourceError::ConfigError(e.to_string()))
    }

    async fn preload(
        &self,
        context: &ResourceContext,
    ) -> ResourceResult<()> {
        for code in context.hierarchy.fallback_order() {
            let _ = self.load_resource(context, &code).await;
        }

        Ok(())
    }

    async fn refresh(&self) -> ResourceResult<()> {
        self.refresh_registry().await.map_err(|e| ResourceError::RegistryError(e.to_string()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn manager_initialises_with_path() {
        let manager = DataSourceManager::with_registry_path("/tmp/test_registry.json");
        manager.initialize_registry().await.unwrap();
    }
}
